// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright @ Huawei Technologies Co., Ltd. 2019-2020. ALL rights reversed.
 */
#include <linux/kernel_stat.h>
#include <linux/time.h>
#include <linux/tick.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/irq.h>
#include <linux/sched/cputime.h>
#include <linux/cache.h>

#include "cpustat.h"

struct cpustat_info {
	struct kernel_stat kstat;
	struct kernel_cpustat cpustat;
	unsigned long timestamp;
};

static struct cpustat_info __percpu *cpustat_data;
#define CPU_IRQS_STAT_NUM 1024
static unsigned int __percpu *cpu_irqs_stat;

#define cpustat_last_softirqs(cpu, i)                                          \
	(per_cpu_ptr(cpustat_data, (cpu))->kstat.softirqs[i])
#define cpustat_last_irqs(cpu, i) (per_cpu_ptr(cpu_irqs_stat, (cpu))[i])
#define cpustat_last_cputime(cpu, i)                                           \
	(per_cpu_ptr(cpustat_data, (cpu))->cpustat.cpustat[i])
#define cpustat_timestamp(cpu) (per_cpu_ptr(cpustat_data, (cpu))->timestamp)

static unsigned int cpustat_cpu_num;
#define cpustat_print(m, args...)                                              \
	do {                                                                   \
		if (m)                                                         \
			seq_printf(m, args);                                   \
		else                                                           \
			pr_warn(args);                                         \
	} while (0)

static u64 cpustat_curr_cputime(int cpu, int index)
{
	u64 time;
	struct kernel_cpustat kcpustat;
	u64 *cpustat = kcpustat.cpustat;

	kcpustat_cpu_fetch(&kcpustat, cpu);

	if (index == CPUTIME_IDLE)
		time = get_idle_time(&kcpustat, cpu);
	else if (index == CPUTIME_IOWAIT)
		time = get_iowait_time_ex(&kcpustat, cpu);
	else
		time = cpustat[index];

	return time;
}

static void cpustat_update_cputime(int cpu)
{
	int i;

	for (i = 0; i < NR_STATS; i++)
		cpustat_last_cputime(cpu, i) = cpustat_curr_cputime(cpu, i);

	cpustat_timestamp(cpu) = ktime_to_ns(ktime_get());
}

static void cpustat_update_softirq(int cpu)
{
	int i;

	for (i = 0; i < NR_SOFTIRQS; i++)
		cpustat_last_softirqs(cpu, i) = kstat_softirqs_cpu(i, cpu);
}

static void cpustat_print_softirq(int cpu, struct seq_file *m)
{
	int i;
	unsigned long count;

	cpustat_print(m, "Sample softirq:\n");
	for (i = 0; i < NR_SOFTIRQS; i++) {
		count = kstat_softirqs_cpu(i, cpu) -
			cpustat_last_softirqs(cpu, i);
		if (count)
			cpustat_print(m, "%12s: %10lu\n", softirq_to_name[i],
				      count);
	}
}

static void cpustat_update_irq(int cpu)
{
	int irq;

	for (irq = 0; irq < CPU_IRQS_STAT_NUM; irq++)
		cpustat_last_irqs(cpu, irq) = kstat_irqs_cpu(irq, cpu);
}

static void cpustat_print_hardirq(int cpu, struct seq_file *m)
{
	int irq;
	unsigned long flag = 0;
	unsigned int stat, delta;
	struct irq_desc *desc;

	cpustat_print(m, "Sample irqstat:\n");
	for_each_irq_desc(irq, desc) {
		if (!desc || irq >= CPU_IRQS_STAT_NUM)
			continue;

		stat = kstat_irqs_cpu(irq, cpu);
		delta = stat - cpustat_last_irqs(cpu, irq);
		if (delta) {
			cpustat_print(
				m, "    irq %4d: delta %10u, curr: %10u, %s\n",
				irq, delta, stat,
				desc->action ? desc->action->name : "none");
			flag++;
		}
	}
	if (flag == 0)
		cpustat_print(m, "    no hard irqs found.\n");
}

void cpustat_update_cpu(void)
{
	int cpu;

	if (!cpustat_data)
		return;
	cpu = smp_processor_id();
	cpustat_update_cputime(cpu);
	cpustat_update_softirq(cpu);
	cpustat_update_irq(cpu);
}

static void cpustat_print_cputime(int cpu, struct seq_file *m)
{
	int i;
	unsigned long time;
	u64 cputime[NR_STATS];

	time = ktime_to_ns(ktime_get()) - cpustat_timestamp(cpu);
	cpustat_print(m, "Sample time: %lu ns(HZ: %d)\n", time, HZ);
	cpustat_print(m, "Sample stat: \n");
	for (i = 0; i < NR_STATS; i++)
		cputime[i] = cpustat_curr_cputime(cpu, i);
	cpustat_print(m,
		      " curr: user: %llu, nice: %llu, sys: %llu, idle: %llu, iowait: %llu, irq: %llu, softirq: %llu, st: %llu\n",
		      cputime[CPUTIME_USER], cputime[CPUTIME_NICE],
		      cputime[CPUTIME_SYSTEM], cputime[CPUTIME_IDLE],
		      cputime[CPUTIME_IOWAIT], cputime[CPUTIME_IRQ],
		      cputime[CPUTIME_SOFTIRQ], cputime[CPUTIME_STEAL]);
	for (i = 0; i < NR_STATS; i++)
		cputime[i] -= cpustat_last_cputime(cpu, i);
	cpustat_print(m,
		      " deta: user: %llu, nice: %llu, sys: %llu, idle: %llu, iowait: %llu, irq: %llu, softirq: %llu, st: %llu\n",
		      cputime[CPUTIME_USER], cputime[CPUTIME_NICE],
		      cputime[CPUTIME_SYSTEM], cputime[CPUTIME_IDLE],
		      cputime[CPUTIME_IOWAIT], cputime[CPUTIME_IRQ],
		      cputime[CPUTIME_SOFTIRQ], cputime[CPUTIME_STEAL]);
}

void cpustat_print_cpu(void)
{
	int cpu;

	if (!cpustat_data)
		return;
	cpu = smp_processor_id();
	cpustat_print_cputime(cpu, NULL);
	cpustat_print_softirq(cpu, NULL);
	cpustat_print_hardirq(cpu, NULL);
}

static ssize_t proc_cpustat_write(struct file *file, const char __user *ubuf,
				  size_t cnt, loff_t *ppos)
{
	unsigned int val;
	int ret;

	ret = kstrtouint_from_user(ubuf, cnt, 0, &val);
	if (ret) {
		pr_err("[cpustat]: parse input parameter failed.\n");
		return ret;
	}

	if (val >= num_possible_cpus()) {
		pr_err("[cpustat]: cpu %d is not in possible cpus!\n", val);
		return -EINVAL;
	}

	if (!cpu_online(val)) {
		pr_err("[cpustat]: cpu %d is not online!\n", val);
		return -EINVAL;
	}

	cpustat_cpu_num = val;

	return cnt;
}

static int cpustat_proc_show(struct seq_file *m, void *s)
{
	unsigned int cpu_num = cpustat_cpu_num;

	if (cpu_num >= num_possible_cpus()) {
		pr_err("[cpustat]: cpu %d is not in possible cpus!\n", cpu_num);
		return -EINVAL;
	}

	if (!cpu_online(cpu_num)) {
		pr_err("[cpustat]: cpu %d is not online!\n", cpu_num);
		return -EINVAL;
	}

	cpustat_print(m, "Sample cpu: %u\n", cpu_num);

	cpustat_print_cputime(cpu_num, m);
	cpustat_print_softirq(cpu_num, m);
	cpustat_print_hardirq(cpu_num, m);
	return 0;
}

static int proc_cpustat_open(struct inode *inode, struct file *file)
{
	return single_open(file, cpustat_proc_show, PDE_DATA(inode));
}

static const struct proc_ops cpustat_proc_ops = {
	.proc_open	= proc_cpustat_open,
	.proc_read	= seq_read,
	.proc_lseek	= seq_lseek,
	.proc_release	= single_release,
	.proc_write	= proc_cpustat_write,
};

void cpustat_init(void)
{
	cpustat_data = __alloc_percpu(sizeof(struct cpustat_info),
				      cache_line_size());
	if (!cpustat_data) {
		pr_err("alloc percpu cpustat_info fail.\n");
		return;
	}
	cpu_irqs_stat = __alloc_percpu(sizeof(unsigned int) * CPU_IRQS_STAT_NUM,
				       sizeof(unsigned int));
	if (!cpu_irqs_stat) {
		pr_err("alloc percpu cpu_irqs_stat fail.\n");
		free_percpu(cpustat_data);
		return;
	}

	if (!proc_create("cpuirqstat", S_IRUSR | S_IWUSR, NULL,
			 &cpustat_proc_ops))
		pr_err("[cpuirqstat]: create /proc/cpuirqstat failed.\n");
}
